﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AzureGames.Model;

using Microsoft.WindowsAzure.StorageClient;

using AutoMapper;
using AzureGames.Data.Implementation.ServiceEntities;

namespace AzureGames.Data.Implementation
{
   
    public class BoardRepository : AzureTableRepository<BoardEntity>, IBoardRepository
    {
        const string PARTITIONKEY = "Board";
        internal BoardPlayerRepository BoardPlayerRepository { get; set; }
        internal BoardPropertyRepository BoardPropertyRepository { get; set; }

        // TODO: support multiple instances and concurrent processing
        //Locking like this won't work once we move into having more than 1 instance.
        //Article below discusses some of the issues
        //http://blog.smarx.com/posts/managing-concurrency-in-windows-azure-with-leases
        object _lock = new object();

        public BoardRepository() : base("Board") 
        {
            BoardPlayerRepository = new Implementation.BoardPlayerRepository();
            BoardPropertyRepository = new Implementation.BoardPropertyRepository();

            AutoMapper.Mapper.CreateMap<Model.Board, ServiceEntities.BoardEntity>()
               .ForMember(m => m.PartitionKey, s => s.UseValue(PARTITIONKEY))
               .ForMember(m => m.RowKey, s => s.MapFrom(g => g.ID));

            AutoMapper.Mapper.CreateMap<ServiceEntities.BoardEntity, Model.Board>();
        }


        public Model.Board GetGameBoard(Guid BoardID)
        {
            var entity = Get(PARTITIONKEY, BoardID.ToString());

            entity.PlayerIDs = BoardPlayerRepository.GetBoardPlayers(BoardID);
            entity.Properties = BoardPropertyRepository.GetBoardProperties(BoardID);

            return entity;
        }


        public Board RegisterNewGame(GameDefinition gameDefinition, string playerID, out bool wasJoined)
        {
            lock (_lock)
            {
                // check if there is an existing game that has not been started and is waiting for players
                Board board = dataContext.CreateQuery<BoardEntity>(this.tableName)
                                                .Where(b => b.GameDefinitionID == gameDefinition.ID && b.State == Board.Created).FirstOrDefault();

                if (board == null)
                {
                    wasJoined = false;

                    // create the game
                    board = new Board
                    {
                        GameDefinitionID = gameDefinition.ID,
                        PlayerIDs = new List<string>(),
                        State = Board.Created,
                        Value = 0,
                        Properties = gameDefinition.StartingProperties,
                        ID = Guid.NewGuid(),
                    };
                    
                    board.PlayerIDs.Add(playerID);

                    if (board.PlayerIDs.Count == gameDefinition.RequiredNumberOfPlayers)
                    {
                        board.State = Board.Active;
                    }

                    AddGameBoard(board);

                }
                else
                {
                    board.PlayerIDs = BoardPlayerRepository.GetBoardPlayers(board.ID);

                    // TODO: this is a workaround when the a register is performed and the user leaves the 
                    //       game page before someone joins
                    if (board.PlayerIDs.Any(pID => pID == playerID))
                        wasJoined = false;
                    else
                    {
                        wasJoined = true;
                        board.PlayerIDs.Add(playerID);

                        if (board.PlayerIDs.Count == gameDefinition.RequiredNumberOfPlayers)
                        {
                            board.State = Board.Active;
                        }

                        UpdateGameBoard(board);
                    }
                }

                return board;
            }
        }

        public void AddGameBoard(Board board)
        {
            Add(board);

            ProcessBoardPlayers(board.ID, board.PlayerIDs);
            ProcessBoardProperties(board.ID, board.Properties);

        }

        public bool UpdateGameBoard(Board board)
        {
            // TODO: research how the entity framework tracks entities\
            //       this code was moved into the add section as an add 
            //       followed by an update was causing a failure 
            //       "entity not tracked"
            var entity = dataContext.Entities.FirstOrDefault(b=> ((BoardEntity)b.Entity).ID==board.ID);
            if (entity == null)
            {
                Update(board);
            }
            else
            {
                var toBoard = entity.Entity as BoardEntity;
                toBoard.State = board.State;
                toBoard.Value = board.Value;

                Update(toBoard);
            }

            ProcessBoardPlayers(board.ID, board.PlayerIDs);
            ProcessBoardProperties(board.ID, board.Properties);

            return true;
        }

        
        public List<Board> GetActiveGames(int gameDefinitionID, string playerID)
        {

            // TODO: this needs to be reworked to be more scalable (maybe at state to boardplayer)
            var response = new List<Board>();
            var activeGames = dataContext.CreateQuery<BoardEntity>(this.tableName)
                .Where(b => b.PartitionKey == PARTITIONKEY && b.GameDefinitionID == gameDefinitionID 
                          && (b.State == Board.Active || b.State == Board.Created));

            foreach (var activeGame in activeGames)
            {
                var players = BoardPlayerRepository.GetBoardPlayers(activeGame.ID);
                if (players.Any(pID => pID == playerID))
                {
                    // implicit conversion
                    Board board = activeGame;
                    board.PlayerIDs = players;
                    board.Properties = BoardPropertyRepository.GetBoardProperties(activeGame.ID);
                    response.Add(board);
                }
            }

            return response;
        }


        private void ProcessBoardProperties(Guid boardID, List<string> properties)
        {
            // order is important so use for loop
            for (int i = 0; i < properties.Count; i++)
            {
                var property = BoardPropertyRepository.GetGameProperty(boardID, i);
                
                if (property == null)
                    BoardPropertyRepository.AddBoardProperty(new BoardPropertyEntity
                    {
                        PartitionKey = "BoardProperty",
                        RowKey = boardID.ToString() +":"+i.ToString(),
                        BoardID = boardID,
                        CreatedDate = DateTime.Now,                        
                        LastUpdatedDate = DateTime.Now,
                        PropertyOrder = i,
                        PropertyValue = properties[i]
                    });
                else
                {
                    if (property.PropertyValue != properties[i])
                    {
                        property.PropertyValue = properties[i];
                        BoardPropertyRepository.UpdateGameProperty(property);
                    }
                }
            }
        }

        private void ProcessBoardPlayers(Guid boardID, List<string> players)
        {
            // order is important so use for loop
            for (int i = 0; i < players.Count; i++)
            {
                var player = BoardPlayerRepository.GetBoardPlayer(boardID, i);

                if (player == null)
                    BoardPlayerRepository.AddBoardPlayer(new BoardPlayerEntity
                    {
                        PartitionKey = "BoardPlayer",
                        RowKey = boardID.ToString() + ":" + i.ToString(),
                        BoardID = boardID,
                        CreatedDate = DateTime.Now,
                        LastUpdatedDate = DateTime.Now,
                        PlayerID = players[i],
                        PlayerOrder = i
                    });
                else
                {
                    if (player.PlayerID != players[i])
                    {
                        player.PlayerID = players[i];
                        BoardPlayerRepository.UpdatBoardPlayer(player);
                    }
                }
            }
        }
    }
}
